﻿using System;
using System.IO;
using System.Net;

namespace MiniProxy
{
    public class MiniProxy : IProxyHook
    {
        private IProxyHook hook;
        public HttpListener Listener { get; private set; }

        public MiniProxy(IProxyHook hook)
        {
            this.hook = hook;
        }

        public void HookRequest(HttpListenerRequest req)
        {
            this.hook.HookRequest(req);
        }

        public void HookResponse(HttpWebResponse resp)
        {
                this.hook.HookResponse(resp);
        }

        public void Stop()
        {
            this.Listener.Stop();
        }

        public void Start(int port)
        {
            this.Listener = new HttpListener();
            this.Listener.Prefixes.Add(string.Format("http://+:{0}/", port.ToString()));
            //listener.Prefixes.Add(string.Format("http://*:{0}/", port.ToString())); //400 bad req?
            this.Listener.Start();
            var arAccept = this.Listener.BeginGetContext(AcceptCallback, this);
        }

        private static void Process(MiniProxy tracy, HttpListenerContext context)
        {
            WebRequest req = WebRequest.Create(context.Request.RawUrl);
            req.Method = context.Request.HttpMethod;

            HttpWebRequest hr = (HttpWebRequest)req;
            Helper.CopyHeaders(hr, context.Request);
            if (context.Request.HasEntityBody)
            {
                Helper.CopyStream(req.GetRequestStream(), context.Request.InputStream);
            }

            ProxyState state = new ProxyState(req, context.Response, tracy);
            req.BeginGetResponse(RespCallback, state);
        }

        private static void AcceptCallback(IAsyncResult ar)
        {
            var tracy = (MiniProxy)ar.AsyncState;
            var ctx = tracy.Listener.EndGetContext(ar);
            tracy.HookRequest(ctx.Request);
            Process(tracy, ctx);

            // continue listening on the port for subsequent connections
            tracy.Listener.BeginGetContext(AcceptCallback, tracy);
        }

        private static void RespCallback(IAsyncResult ar)
        {
            // State of request is of predefined type
            ProxyState state = (ProxyState)ar.AsyncState;

            using (WebResponse resp = state.Req.EndGetResponse(ar))
            using (Stream stm = resp.GetResponseStream())
            {
                state.Hook.HookResponse((HttpWebResponse)resp);

                Helper.CopyResponseHeaders(state.Resp, resp);
                // bytesCopied should equal to Content-Length (except for -1)
                Helper.CopyStream(state.Resp.OutputStream, stm);
                state.Resp.OutputStream.Close();
            }
        }
    }
}
